<?php

namespace app\logic\provider;

use JetBrains\PhpStorm\ArrayShape;
use Overtrue\Socialite\Contracts;
use Overtrue\Socialite\Exceptions\BadRequestException;
use Overtrue\Socialite\Providers\Base;
use Overtrue\Socialite\User;

class Twitter extends Base
{
    public const NAME = 'twitter';

    public  ?string $state = 'state';

    public array $scopes = ['users.read', 'tweet.read', /*'account.follows.read', 'account.follows.write'*/];

    public string $scopeSeparator = " ";

    /**
     * 获取认证URL
     *
     * 本方法用于构建并返回一个OAuth2认证URL该URL用于引导用户进行认证，以获取用户授权
     * 认证URL中包含了必要的查询参数，如response_type、client_id、redirect_uri、scope、state、code_challenge等
     * 这些参数是根据OAuth2规范来使用的，用于确保认证流程的安全性和正确性
     *
     * @return string 返回构建好的认证URL
     */
    protected function getAuthUrl(): string
    {
        //https://twitter.com/i/oauth2/authorize?response_type=code&client_id=M1M5R3BMVy13QmpScXkzTUt5OE46MTpjaQ&redirect_uri=https://www.example.com&scope=tweet.read%20users.read%20offline.access&state=state&code_challenge=challenge&code_challenge_method=plain
        return $this->buildAuthUrlFromBase('https://twitter.com/i/oauth2/authorize');
    }

    /**
     * 根据基础URL构建授权URL
     *
     * 该方法用于生成OAuth 2.0授权流程中所需的授权URL它通过在基础URL上附加查询参数来构建最终的授权URL
     * 查询参数包括客户端ID、重定向URI、响应类型（通常是code）以及可选的状态参数状态参数用于防止CSRF攻击
     *
     * @param string $url OAuth 2.0授权服务器的基础URL
     * @return string 返回构建好的授权URL
     */
    protected function buildAuthUrlFromBase(string $url): string
    {
        $query = $this->getCodeFields() + ($this->state ? [Contracts\RFC6749_ABNF_STATE => $this->state] : []);
        $url =  $url.'?'.\http_build_query($query, '', '&', $this->encodingType);
        // 由于OAuth 2.0规范要求，将查询字符串中的加号替换为空格的URL编码形式（%20）
        return str_replace('+', '%20', $url);
    }

    /**
     * 获取获取令牌的URL
     *
     * 此方法返回一个固定的URL地址，用于在OAuth2.0流程中获取访问令牌
     * 该URL是向API服务器请求令牌的端点
     *
     * @return string 返回令牌URL地址
     */
    protected function getTokenUrl(): string
    {
        return 'https://api.x.com/2/oauth2/token';
    }


    /**
     * 通过授权码获取访问令牌
     *
     * 此方法使用给定的授权码发起请求，以获取访问令牌信息它通过HTTP客户端发送请求，
     * 并对返回的响应进行处理和格式化，以便进一步使用或处理
     *
     * @param string $code 授权码，用于获取访问令牌
     * @return array 格式化后的访问令牌响应数据
     */
    public function tokenFromCode(string $code): array
    {
        $response = $this->getHttpClient()->post($this->getTokenUrl(), [
            'form_params' => $this->getTokenFields($code),
        ]);
        return $this->normalizeAccessTokenResponse($response->getBody());
    }

    /**
     * 获取获取令牌所需的字段
     *
     * 此函数返回一个数组，包含了根据OAuth 2.0规范（RFC 6749）构造的令牌请求参数
     * 数组中的键对应于规范中的参数名称，而值则是相应的参数值
     *
     * @param string $code 授权代码，用于换取令牌
     * @return array 返回一个数组，包含了客户端ID、重定向URI、授权类型和code_verifier等信息
     */
    #[ArrayShape([
        Contracts\RFC6749_ABNF_CLIENT_ID => "null|string",
        Contracts\RFC6749_ABNF_CODE => "string",
        Contracts\RFC6749_ABNF_GRANT_TYPE => "string",
        Contracts\RFC6749_ABNF_REDIRECT_URI => "mixed|null|string",
        'code_verifier' => "string"
    ])]
    protected function getTokenFields(string $code): array
    {
        // 返回令牌请求所需的字段数组
        return [
            Contracts\RFC6749_ABNF_CODE => $code,
            Contracts\RFC6749_ABNF_CLIENT_ID => $this->getClientId(),
            Contracts\RFC6749_ABNF_REDIRECT_URI => $this->redirectUrl,
            Contracts\RFC6749_ABNF_GRANT_TYPE => Contracts\RFC6749_ABNF_AUTHORATION_CODE,
            'code_verifier' => 'challenge',
        ];
    }

    /**
     * 根据给定的认证令牌获取用户信息
     *
     * 本函数通过发送HTTP GET请求到指定的API端点，来获取与给定令牌相关联的用户信息
     * 它首先构造了一个HTTP客户端，然后设置了请求头部，包括内容类型和授权令牌，
     * 最后发送请求并解析响应体为PHP数组格式，返回用户信息
     *
     * @param string $token 用户的认证令牌，用于向API表明用户的身份
     *
     * @return array 包含用户信息的数组，具体结构取决于API的响应
     */
    protected function getUserByToken(string $token): array
    {
        // 发起HTTP GET请求，获取用户信息
        $responseInstance = $this->getHttpClient()->get('https://api.x.com/2/users/me', [
            'headers' => [
                'Accept' => 'application/json',
                'Authorization' => 'Bearer '.$token,
            ],
        ]);
        $response = $this->fromJsonBody($responseInstance);

        if (! empty($response['errors'])) {
            throw new BadRequestException((string) $responseInstance->getBody());
        }

        return $response['data'];
    }

    /**
     * 将用户数组映射到用户对象
     *
     * 此函数负责将给定的用户信息数组转换为一个用户对象（UserInterface实例）
     * 它映射了用户信息中的各个字段到用户对象的相应属性
     *
     * @param array $user 包含用户信息的数组，如从数据库或API响应中获取的数据
     * @return Contracts\UserInterface 映射后的用户对象，实现了UserInterface接口
     */
    protected function mapUserToObject(array $user): Contracts\UserInterface
    {
        // 创建一个新的User对象，并传入一个数组，该数组包含了用户的各种属性
        // 使用null合并运算符（??）来确保如果给定的用户信息数组中缺少某个字段，该字段将被设置为null
        return new User([
            // 映射ABNF_ID字段
            Contracts\ABNF_ID => $user[Contracts\ABNF_ID] ?? null,
            // 映射用户名字段，如果email字段不存在，则设置为null
            'username' => $user['username'] ?? null,
            // 映射昵称字段
            Contracts\ABNF_NICKNAME => $user[Contracts\ABNF_NAME] ?? null,
            // 映射姓名字段
            Contracts\ABNF_NAME => $user[Contracts\ABNF_NAME] ?? null,
            // 映射邮箱字段
            Contracts\ABNF_EMAIL => $user[Contracts\ABNF_EMAIL] ?? null,
            // 映射头像字段，注意这里使用了不同的键（'picture'），这可能是因为数据源中的字段名与接口要求的字段名不一致
            Contracts\ABNF_AVATAR => $user['picture'] ?? null,
        ]);
    }

    /**
     * 获取代码字段数组
     *
     * 该方法继承自父类，并添加了scope、code_challenge和code_challenge_method字段
     * 主要用于OAuth2流程中，生成授权请求所需的参数列表
     *
     * @return array 包含所有必要字段的关联数组
     */
    protected function getCodeFields(): array
    {
        // 继承父类的getCodeFields方法，获取基本字段数组
        $fields = parent::getCodeFields();

        // 将scope数组使用scopeSeparator连接成字符串，便于传输
        $fields['scope'] = implode($this->scopeSeparator, $this->scopes);

        // 设置code_challenge为一个固定的值'challenge'
        // 这里简化了处理，实际应用中可能需要根据实际情况生成或获取
        $fields['code_challenge'] = 'challenge';

        // 指定code_challenge的处理方法为'plain'
        // 这表明code_challenge是明文形式，没有进行额外的加密或转换
        $fields['code_challenge_method'] = 'plain';

        // 返回包含所有字段的数组
        return $fields;
    }
}